#if !__has_feature(objc_arc)  
    #error ORSSerialPortManager.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for ORSSerialPortManager.m in the Build Phases for this target  
#endif  
  
#import "ORSSerialPortManager.h"  
#import "ORSSerialPort.h"  
  
#import <IOKit/IOKitLib.h>  
#import <IOKit/serial/IOSerialKeys.h>  
  
#ifdef LOG_SERIAL_PORT_ERRORS  
    #define LOG_SERIAL_PORT_ERROR(fmt, ...) NSLog(fmt, ##__VA_ARGS__)  
#else  
    #define LOG_SERIAL_PORT_ERROR(fmt, ...)  
#endif  
  
NSString * const ORSSerialPortsWereConnectedNotification = @"ORSSerialPortWasConnectedNotification";  
NSString * const ORSSerialPortsWereDisconnectedNotification = @"ORSSerialPortWasDisconnectedNotification";  
  
NSString * const ORSConnectedSerialPortsKey = @"ORSConnectedSerialPortsKey";  
NSString * const ORSDisconnectedSerialPortsKey = @"ORSDisconnectedSerialPortsKey";  
  
void ORSSerialPortManagerPortsPublishedNotificationCallback(voidvoid *refCon, io_iterator_t iterator);  
void ORSSerialPortManagerPortsTerminatedNotificationCallback(voidvoid *refCon, io_iterator_t iterator);  
  
@interface ORSSerialPortManager ()  
  
@property (nonatomic, copy, readwrite) NSArray *availablePorts;  
@property (nonatomic, strong) NSMutableArray *portsToReopenAfterSleep;  
  
@property (nonatomic) io_iterator_t portPublishedNotificationIterator;  
@property (nonatomic) io_iterator_t portTerminatedNotificationIterator;  
  
@end  
  
static ORSSerialPortManager *sharedInstance = nil;  
  
@implementation ORSSerialPortManager  
{  
    NSMutableArray *_availablePorts;  
}  
  
#pragma mark - Singleton Methods  
  
- (id)init  
{  
    if (self == sharedInstance)  
        return sharedInstance; // Already initialized  
      
    self = [super init];  
    if (self != nil)  
    {  
        self.portsToReopenAfterSleep = [NSMutableArray array];  
          
        [self retrieveAvailablePortsAndRegisterForChangeNotifications];  
        [self registerForNotifications];  
    }  
    return self;  
}  
  
+ (ORSSerialPortManager *)sharedSerialPortManager;  
{  
    static dispatch_once_t onceToken;  
    dispatch_once(&onceToken, ^{  
        if (sharedInstance == nil)  
            sharedInstance = [(ORSSerialPortManager *)[super allocWithZone:NULL] init];  
    });  
      
    return sharedInstance;  
}  
  
+ (id)allocWithZone:(NSZone *)zone  
{  
    return [self sharedSerialPortManager];  
}  
  
- (id)copyWithZone:(NSZone *)zone  
{  
    return self;  
}  
  
- (void)dealloc  
{  
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];  
    [nc removeObserver:self];  
      
    // Stop IOKit notifications for ports being added/removed  
    IOObjectRelease(_portPublishedNotificationIterator);  
    _portPublishedNotificationIterator = 0;  
    IOObjectRelease(_portTerminatedNotificationIterator);  
    _portTerminatedNotificationIterator = 0;  
}  
  
- (void)registerForNotifications  
{  
    // register for notifications (only if AppKit is available)  
    void (^terminationBlock)(void) = ^{  
        for (ORSSerialPort *eachPort in self.availablePorts) [eachPort cleanupAfterSystemRemoval];  
        self.availablePorts = nil;  
    };  
      
#ifdef NSAppKitVersionNumber10_0  
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];  
    [nc addObserverForName:NSApplicationWillTerminateNotification object:nil queue:nil usingBlock:^(NSNotification *notification){  
        // For some unknown reason, this notification fires twice,  
        // doesn't cause a problem right now, but be aware  
        terminationBlock();  
    }];  
      
    [nc addObserver:self selector:@selector(systemWillSleep:) name:NSWorkspaceWillSleepNotification object:NULL];  
    [nc addObserver:self selector:@selector(systemDidWake:) name:NSWorkspaceDidWakeNotification object:NULL];  
#else  
    // If AppKit isn't available, as in a Foundation command-line tool, cleanup upon exit. Sleep/wake  
    // notifications don't seem to be available without NSWorkspace.  
    int result = atexit_b(terminationBlock);  
    if (result) NSLog(@"ORSSerialPort was unable to register its termination handler for serial port cleanup: %i", errno);  
#endif  
}  
  
#pragma mark - Public Methods  
  
#pragma mark -  
#pragma Sleep/Wake Management  
  
- (void)systemWillSleep:(NSNotification *)notification;  
{  
    NSArray *ports = self.availablePorts;  
    for (ORSSerialPort *port in ports)  
    {  
        if (port.isOpen)  
        {  
            if ([port close])  
                [self.portsToReopenAfterSleep addObject:port];  
        }  
    }  
}  
  
- (void)systemDidWake:(NSNotification *)notification;  
{  
    NSArray *portsToReopen = [self.portsToReopenAfterSleep copy];  
    for (ORSSerialPort *port in portsToReopen)  
    {  
        [port open];  
        [self.portsToReopenAfterSleep removeObject:port];  
    }  
}  
  
#pragma mark - Private Methods  
  
- (void)serialPortsWerePublished:(io_iterator_t)iterator;  
{  
    NSMutableArray *newlyConnectedPorts = [[NSMutableArray alloc] init];  
    io_object_t device;  
    while ((device = IOIteratorNext(iterator)))  
    {  
        ORSSerialPort *port = [[ORSSerialPort alloc] initWithDevice:device];  
        if (![self.availablePorts containsObject:port]) [newlyConnectedPorts addObject:port];  
        IOObjectRelease(device);  
    }  
      
    [[self mutableArrayValueForKey:@"availablePorts"] addObjectsFromArray:newlyConnectedPorts];  
      
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];  
    NSDictionary *userInfo = @{ORSConnectedSerialPortsKey : newlyConnectedPorts};  
    [nc postNotificationName:ORSSerialPortsWereConnectedNotification object:self userInfo:userInfo];  
}  
  
- (void)serialPortsWereTerminated:(io_iterator_t)iterator;  
{  
    NSMutableArray *newlyDisconnectedPorts = [[NSMutableArray alloc] init];  
    io_object_t device;  
    while ((device = IOIteratorNext(iterator)))  
    {  
        ORSSerialPort *port = [[ORSSerialPort alloc] initWithDevice:device];  
        [newlyDisconnectedPorts addObject:port];  
        IOObjectRelease(device);  
    }  
      
    [newlyDisconnectedPorts makeObjectsPerformSelector:@selector(cleanupAfterSystemRemoval)];  
    [[self mutableArrayValueForKey:@"availablePorts"] removeObjectsInArray:newlyDisconnectedPorts];  
      
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];  
    NSDictionary *userInfo = @{ORSDisconnectedSerialPortsKey : newlyDisconnectedPorts};  
    [nc postNotificationName:ORSSerialPortsWereDisconnectedNotification object:self userInfo:userInfo];  
}  
  
- (void)retrieveAvailablePortsAndRegisterForChangeNotifications;  
{  
    IONotificationPortRef notificationPort = IONotificationPortCreate(kIOMasterPortDefault);  
    CFRunLoopAddSource(CFRunLoopGetCurrent(),IONotificationPortGetRunLoopSource(notificationPort),kCFRunLoopDefaultMode);  
      
    CFMutableDictionaryRef matchingDict = NULL;  
      
    matchingDict = IOServiceMatching(kIOSerialBSDServiceValue);  
    CFRetain(matchingDict); // Need to use it twice  
      
    CFDictionaryAddValue(matchingDict, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes));  
      
    io_iterator_t portIterator = 0;  
    kern_return_t result = IOServiceAddMatchingNotification(notificationPort,kIOPublishNotification,matchingDict,ORSSerialPortManagerPortsPublishedNotificationCallback,(__bridge voidvoid *)(self),// refCon/contextInfo  
        &portIterator);  
    if (result){  
        LOG_SERIAL_PORT_ERROR(@"Error getting serialPort list:%i", result);  
        if (portIterator) IOObjectRelease(portIterator);  
        CFRelease(matchingDict); // Above call to IOServiceAddMatchingNotification consumes one reference, but we added a retain for the below call  
        return;  
    }  
      
    self.portPublishedNotificationIterator = portIterator;  
    IOObjectRelease(portIterator);  
      
    NSMutableArray *ports = [NSMutableArray array];  
    io_object_t eachPort;  
    while ((eachPort = IOIteratorNext(self.portPublishedNotificationIterator)))  
    {  
        ORSSerialPort *port = [ORSSerialPort serialPortWithDevice:eachPort];  
        if (port) {  
            [ports addObject:port];  
        }  
        IOObjectRelease(eachPort);  
    }  
      
    self.availablePorts = ports;  
      
    // Also register for removal  
    IONotificationPortRef terminationNotificationPort = IONotificationPortCreate(kIOMasterPortDefault);  
    CFRunLoopAddSource(CFRunLoopGetCurrent(),IONotificationPortGetRunLoopSource(terminationNotificationPort),kCFRunLoopDefaultMode);  
    result = IOServiceAddMatchingNotification(terminationNotificationPort,kIOTerminatedNotification,matchingDict,ORSSerialPortManagerPortsTerminatedNotificationCallback,(__bridge voidvoid *)(self),// refCon/contextInfo  
        &portIterator);  
    if (result) {  
        LOG_SERIAL_PORT_ERROR(@"Error registering for serial port termination notification:%i.", result);  
        if (portIterator) IOObjectRelease(portIterator);  
        return;  
    }  
      
    self.portTerminatedNotificationIterator = portIterator;  
    IOObjectRelease(portIterator);  
      
    while (IOIteratorNext(self.portTerminatedNotificationIterator)) {  
    }; // Run out the iterator or notifications won't start  
}  
  
#pragma mark - Properties  
  
@synthesize availablePorts = _availablePorts;  
  
- (void)setAvailablePorts:(NSArray *)ports  
{  
    if (ports != _availablePorts)  
    {  
        _availablePorts = [ports mutableCopy];  
    }  
}  
  
//可用的串口数量  
- (NSUInteger)countOfAvailablePorts {  
    return [_availablePorts count];  
}  
  
//返回某个串口  
- (id)objectInAvailablePortsAtIndex:(NSUInteger)index {  
    return [_availablePorts objectAtIndex:index];  
}  
  
//插入多个串口  
- (void)insertAvailablePorts:(NSArray *)array atIndexes:(NSIndexSet *)indexes {  
    [_availablePorts insertObjects:array atIndexes:indexes];  
}  
  
//插入单个串口  
- (void)insertObject:(ORSSerialPort *)object inAvailablePortsAtIndex:(NSUInteger)index {  
    [_availablePorts insertObject:object atIndex:index];  
}  
  
//移除多个串口  
- (void)removeAvailablePortsAtIndexes:(NSIndexSet *)indexes { [_availablePorts removeObjectsAtIndexes:indexes];  
}  
  
//移除单个串口  
- (void)removeObjectFromAvailablePortsAtIndex:(NSUInteger)index {  
    [_availablePorts removeObjectAtIndex:index];  
}  
  
@synthesize portsToReopenAfterSleep = _portsToReopenAfterSleep;  
  
@synthesize portPublishedNotificationIterator = _portPublishedNotificationIterator;  
- (void)setPortPublishedNotificationIterator:(io_iterator_t)iterator  
{  
    if (iterator != _portPublishedNotificationIterator)  
    {  
        if (_portPublishedNotificationIterator)  
            IOObjectRelease(_portPublishedNotificationIterator);  
          
        _portPublishedNotificationIterator = iterator;  
        IOObjectRetain(_portPublishedNotificationIterator);  
    }  
}  
  
@synthesize portTerminatedNotificationIterator = _portTerminatedNotificationIterator;  
- (void)setPortTerminatedNotificationIterator:(io_iterator_t)iterator  
{  
    if (iterator != _portTerminatedNotificationIterator)  
    {  
        if (_portTerminatedNotificationIterator) IOObjectRelease(_portTerminatedNotificationIterator);  
          
        _portTerminatedNotificationIterator = iterator;  
        IOObjectRetain(_portTerminatedNotificationIterator);  
    }  
}  
  
@end  
  
void ORSSerialPortManagerPortsPublishedNotificationCallback(voidvoid *refCon, io_iterator_t iterator)  
{  
    ORSSerialPortManager *manager = (__bridge ORSSerialPortManager *)refCon;  
    if (![manager isKindOfClass:[ORSSerialPortManager class]])  
    {  
        NSLog(@"Unexpected context object %@ in %s. Context object should be an instance of ORSSerialPortManager", manager, __PRETTY_FUNCTION__);  
        return;  
    }  
    [manager serialPortsWerePublished:iterator];  
}  
  
void ORSSerialPortManagerPortsTerminatedNotificationCallback(voidvoid *refCon, io_iterator_t iterator)  
{  
    ORSSerialPortManager *manager = (__bridge ORSSerialPortManager *)refCon;  
    if (![manager isKindOfClass:[ORSSerialPortManager class]])  
    {  
        NSLog(@"Unexpected context object %@ in %s. Context object should be an instance of ORSSerialPortManager", manager, __PRETTY_FUNCTION__);  
        return;  
    }  
    [manager serialPortsWereTerminated:iterator];  
} 
